/**
 * Copyright 2018 orivil.com. All rights reserved.
 * Use of this source code is governed by a MIT-style
 * license that can be found at https://mit-license.org.
 */

package mux

import (
	"html/template"
	"net/http"
	"sort"
	"strings"
	"sync"
	"path/filepath"
	"fmt"
)

var TemplateDir = "views"

var TemplateExt = ".gohtml"

type TemplatePathExistError struct {
	Path string
}

func (te *TemplatePathExistError) Error() string {
	return fmt.Sprintf("路径 %s 重复", te.Path)
}

type TemplateContainer struct {
	tpls Templates
}

// 获取站点子页面, 有安全限制
func (tc *TemplateContainer) GetSubTemplates(parentPath string, userLevel int) (ts Templates) {
	tpl := tc.Match(parentPath)
	if tpl.Level <= userLevel {
		return getSubTpls(tpl, userLevel)
	}
	return
}

// 获取站点顶级页面, 有安全限制
func (tc *TemplateContainer) GetTopTemplates(basePath string, userLevel int) Templates {
	var ts Templates
	for _, tpl := range tc.tpls {
		if strings.HasPrefix(tpl.Path, basePath) {
			if tpl.Level <= userLevel {
				ts = append(ts, &Template{
					Name:  tpl.Name,
					Path:  tpl.Path,
					Level: tpl.Level,
				})
			}
		}
	}
	return ts
}

// 获取站点地图, 有安全限制
func (tc *TemplateContainer) GetAllTemplates(userLevel int) Templates {
	return getSiteMap("", tc.tpls, userLevel)
}

func getSubTpls(tpl *Template, userLevel int) Templates {
	var tpls Templates
	for _, sub := range tpl.Subs {
		if sub.Level <= userLevel {
			tpls = append(tpls, &Template{
				Name: sub.Name,
				Level: sub.Level,
				Path: sub.Path,
				Subs: getSubTpls(sub, userLevel),
				Views: sub.Views,
			})
		}
	}
	return tpls
}

func getSiteMap(parentPath string, tpls Templates, userLevel int) Templates {
	var ts Templates
	for _, tpl := range tpls {
		if tpl.Level <= userLevel {
			ts = append(ts, &Template{
				Name: tpl.Name,
				Level: tpl.Level,
				Path: parentPath + tpl.Path,
				Subs: getSiteMap(parentPath + tpl.Path, tpl.Subs, userLevel),
			})
		}
	}
	return ts
}

func (tc *TemplateContainer) AddTemplates(tpls Templates) error {
	tc.tpls = append(tc.tpls, tpls...)
	return initTpls(tc.tpls)
}

func initTpls(ts Templates) error {
	existPath := make(map[string]struct{}, len(ts))
	for _, t := range ts {
		// 检查 path 是否重复注册
		if _, ok := existPath[t.Path]; ok {
			return &TemplatePathExistError{Path:t.Path}
		} else {
			existPath[t.Path] = struct{}{}
		}
		// 将父 dir 传递给子 dir, 或拼接父 dir 与子 dir
		for _, sub := range t.Subs {
			if sub.Dir == "" {
				sub.Dir = t.Dir
			} else if t.Dir != "" {
				sub.Dir = filepath.Join(t.Dir, sub.Dir)
			}
			// 子目录权限不能低于父目录权限
			if sub.Level < t.Level {
				sub.Level = t.Level
			}
		}
	}

	for _, tpl := range ts {
		if tpl.Subs != nil {
			initTpls(tpl.Subs)
		}
		tpl.initFiles()
	}
	// 排序
	sort.Sort(ts)
	return nil
}

func (tc *TemplateContainer) Match(path string) *Template {
	for _, tpl := range tc.tpls {
		if strings.HasPrefix(path, tpl.Path) {
			subPath := strings.TrimPrefix(path, tpl.Path)
			t := tpl.Match(subPath)
			if t != nil {
				return t
			}
		}
	}
	return nil
}

type Templates []*Template

func (t Templates) Len() int {
	return len(t)
}

func (t Templates) Less(i, j int) bool {
	return t[i].Sort < t[j].Sort
}

func (t Templates) Swap(i, j int) {
	t[i], t[j] = t[j], t[i]
}

type Template struct {
	Name    string
	Path    string
	Views   []string
	Dir     string
	Styles  []string
	Scripts []string
	Sort    int
	Level   int
	Subs    Templates
	files   []string
	tpl     *template.Template
	sync.RWMutex
}

func (tpl *Template) Serve(w http.ResponseWriter, r *http.Request, debug bool) error {
	var t *template.Template
	cacheTpl := r.URL.Query().Get("cache") == "1"
	if cacheTpl {
		tpl.RLock()
		t = tpl.tpl
		tpl.RUnlock()
	}
	if t == nil {
		temp, err := template.ParseFiles(tpl.files...)
		if err != nil {
			return err
		}
		t = temp
		if cacheTpl {
			tpl.Lock()
			tpl.tpl = t
			tpl.Unlock()
		}
	}
	return t.Execute(w, map[string]interface{}{
		"debug":   debug,
		"scripts": tpl.Scripts,
		"styles":  tpl.Styles,
	})
}

func (tpl *Template) initFiles() {
	if tpl.files == nil {
		tpl.files = make([]string, len(tpl.Views))
		for key, view := range tpl.Views {
			tpl.files[key] = filepath.Join(TemplateDir, tpl.Dir, view+TemplateExt)
		}
	}
}

func (tpl *Template) Append(subs ...*Template) *Template {
	tpl.Subs = append(tpl.Subs, subs...)
	return tpl
}

func (tpl *Template) Match(subPath string) *Template {
	if subPath == "" {
		return tpl
	} else {
		for _, tpl := range tpl.Subs {
			if strings.HasPrefix(subPath, tpl.Path) {
				subPath = strings.TrimPrefix(subPath, tpl.Path)
				t := tpl.Match(subPath)
				if t != nil {
					return t
				}
			}
		}
	}
	return nil
}